/*---------------------------------------------------------------------------------------
Filename: fluid.c
Description: Android implementation of Jos Stam's fluid solver for Android NDK
http://www.dgp.toronto.edu/~stam/reality/Research/pub.html

Copyright (c) 2010, droidViz Project Maintainers
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------------------------*/
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include <assert.h>

#include "importgl.h"
#include "app.h"
#include "solver.h"
#include "glu.h"
#include "shaders.h"

// Defines ---------------------------------------------------------------------------

// Simple define for matrix indexing
#define IX(i,j) ((i)+(N+2)*(j))

//Defines for Drawing Circles
#define DEGREES_TO_RADIANS(x) (3.14159265358979323846 * x / 180.0)
#define RANDOM_FLOAT_BETWEEN(x, y) (((float) rand() / RAND_MAX) * (y - x) + x)

// Enumeration which makes accessing hardware buffers easier.
enum
{
	INDEX_OBJECT = 0,
    POSITION_OBJECT = 1,
    COLOR_OBJECT = 2
};

// Globals ---------------------------------------------------------------------------
static int N;
static float dt, diff, visc;
static float force, source;
static int dvel;
static int density_alpha;

static float * u, * v, * u_prev, * v_prev;
static float * dens, * dens_prev;
float * line_vertices; float * density_colors; float * density_vertices;
unsigned short * line_indices; unsigned short * density_indices;

static int win_x, win_y;
static int mouse_down[3];
static int omx, omy, mx, my;
static float p_x, p_y;

int  velocity_vbo[2]; // velocity_vbo[COLOR_OBJECT] does not exist!
int  density_vbo[3];

float circle_vertices[720]; // Circle Vertices
int circle_vbo;

int buffers_allocated;
int buffers_supported;

GLuint gProgram; // Shader Program
GLuint gvPositionHandle, gvColorHandle, gVelocityHandle; // Handle to vertex attributes

// GL Debugging --------------------------------------------------------
static void printGLString(const char *name, GLenum s) 
{
    const char *v = (const char *) glGetString(s);
    dprintf(0, "GL %s = %s\n", name, v);
}

// Memory Allocation/Freeing --------------------------------------------------------
static void free_hardware_buffers( void )
{
	// Free hardware buffers
	if ( buffers_allocated )
	{
		glDeleteBuffers( 2, velocity_vbo );
		glDeleteBuffers( 3, density_vbo );
	}
	buffers_allocated = 0;
}

static void free_data ( void )
{
	// Free simulation data
	if ( u ) free ( u );
	if ( v ) free ( v );
	if ( u_prev ) free ( u_prev );
	if ( v_prev ) free ( v_prev );
	if ( dens ) free ( dens );
	if ( dens_prev ) free ( dens_prev );

	// Free rendering arrays
	if ( line_vertices ) free ( line_vertices );
	if ( line_indices ) free ( line_indices );
	if ( density_colors ) free ( density_colors );
	if ( density_vertices ) free ( density_vertices );
	if ( density_indices ) free ( density_indices );

	// Free hardware buffers
	free_hardware_buffers();
}

static void clear_data ( void )
{
	int i, j, size=(N+2)*(N+2);

	for ( i=0 ; i<size ; i++ ) {
		u[i] = v[i] = u_prev[i] = v_prev[i] = dens[i] = dens_prev[i] = 0.0f;
	}

	// Clear out the specially sized rendering stuff
	for ( i=0 ; i<size*2 ; i++ )
		line_indices[i] = 0;
	for ( i=0 ; i<size*4 ; i++ )
		line_vertices[i] = 0.0f;
	for ( i=0 ; i<size*6 ; i++ )
		density_indices[i] = 0;
	for ( i=0 ; i<size*12 ; i++ )
		density_vertices[i] = 0.0f;
	for ( i=0 ; i<size*24 ; i++ )
		density_colors[i] = 0.0f;
}

static int allocate_data ( void )
{
	int size = (N+2)*(N+2);

	u			= (float *) malloc ( size*sizeof(float) );
	v			= (float *) malloc ( size*sizeof(float) );
	u_prev		= (float *) malloc ( size*sizeof(float) );
	v_prev		= (float *) malloc ( size*sizeof(float) );
	dens		= (float *) malloc ( size*sizeof(float) );	
	dens_prev	= (float *) malloc ( size*sizeof(float) );

	// Rendering structures
	// N X N grid of vectors, each vector needs 4 floats to define (x,y)(x,y)
	// Technically we allocate (N+2)*(N+2) vectors, but this allows for wiggle room and optimization later.
	line_vertices = (float *) malloc ( size*4*sizeof(float) );
	line_indices = (unsigned short *) malloc ( size*2*sizeof(unsigned short) );
	density_colors = (float *) malloc ( size*24*sizeof(float) );
	density_vertices = (float *) malloc ( size*12*sizeof(float) );
	density_indices = (unsigned short *) malloc ( size*6*sizeof(unsigned short) );

	if ( !u || !v || !u_prev || !v_prev || !dens || !dens_prev || !line_vertices || !density_vertices || !density_colors || !density_indices ) 
	{
		errprintf ( "cannot allocate data\n" );
		return ( 0 );
	}

	return ( 1 );
}

// Create the vector index buffer
static void setup_velocity( void )
{
	// Setup the vector line index buffer
	// Setup velocity index buffer 
	unsigned short i = 0, j = 0, x, y;

	for (x = 1; x <= N; x++) {
		for (y = 1; y <= N; y++) {			
			line_indices[i++] = j++;
			line_indices[i++] = j++;
		}
	}
}

// Create the density index buffer
static void setup_density( void )
{
	// Setup density index buffer 
	int i, j, k, m;
	float x, y;
	unsigned short a, b, c, d;
	float h = (1.0f/N)*2;

	k = 0;
	m = 0;
	int zz = 0;

	for (i = 0; i <= N; i++) {
		x = (i-0.5f)*h-1;
		for (j = 0; j <= N; j++) {
			y = (j-0.5f)*h-1;
			
			// This may be an optimization to make later,
			// but it currently doesnt work.
			a = (unsigned short) IX(i,j);
			b = (unsigned short) IX(i,j+1);
			c = (unsigned short) IX(i+1,j);
			d = (unsigned short) IX(i+1,j+1);

			density_vertices[m++] = x;
			density_vertices[m++] = y;
			density_indices[k++] = zz++;

			density_vertices[m++] = x+h;
			density_vertices[m++] = y;
			density_indices[k++] = zz++;

			density_vertices[m++] = x;
			density_vertices[m++] = y+h;
			density_indices[k++] = zz++;

			density_vertices[m++] = x;
			density_vertices[m++] = y+h;
			density_indices[k++] = zz++;
			
			density_vertices[m++] = x+h;
			density_vertices[m++] = y;
			density_indices[k++] = zz++;
			
			density_vertices[m++] = x+h;
			density_vertices[m++] = y+h;
			density_indices[k++] = zz++;
		}
	}
}

static void setup_hardware_buffers ( void )
{
	// Initialize index buffers for both
	setup_velocity();
	setup_density();

	// Allocate two separate buffer sets: one for velocity, one for density
	int size = (N+2)*(N+2);
	if( !buffers_allocated && buffers_supported )
	{
		glGenBuffers(2, velocity_vbo);
		glGenBuffers(3, density_vbo);

		// Velocity:
		// Allocate and fill Index buffer
		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, velocity_vbo[INDEX_OBJECT] );
		glBufferData( GL_ELEMENT_ARRAY_BUFFER, size*2*sizeof(unsigned short), line_indices, GL_STATIC_DRAW );

		// Allocate and fill Vertex buffer
		glBindBuffer( GL_ARRAY_BUFFER, velocity_vbo[POSITION_OBJECT] );
		glBufferData( GL_ARRAY_BUFFER, size*4*sizeof(float), line_vertices, GL_DYNAMIC_DRAW );

		// Density:
		// Allocate and fill Index buffer
		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, density_vbo[INDEX_OBJECT] );
		glBufferData( GL_ELEMENT_ARRAY_BUFFER, size*6*sizeof(unsigned short), density_indices, GL_STATIC_DRAW );

		// Allocate and fill Vertex buffer
		glBindBuffer( GL_ARRAY_BUFFER, density_vbo[POSITION_OBJECT] );
		glBufferData( GL_ARRAY_BUFFER, size*12*sizeof(float), density_vertices, GL_STATIC_DRAW );
		
		// Allocate and fill Color buffer
		glBindBuffer( GL_ARRAY_BUFFER, density_vbo[COLOR_OBJECT] );
		glBufferData( GL_ARRAY_BUFFER, size*24*sizeof(float), density_colors, GL_DYNAMIC_DRAW );

		buffers_allocated = 1;
	}
	if( !buffers_supported )
		dprintf(0, "Hardware buffers not supported, call to setup_hardware_buffers disregareded.\n" );
}

// Per-Frame Update Functions ---------------------------------------------------------

// Update the velocity vertices
static void update_velocity( void )
{
	int i, j, k;
	float x, y, h;
	float* buffer = NULL;
	int size = (N)*(N);

	// This is how spaced apart the points are
	h = (1.0f/N)*2;

	// An index for the vertex array
	k = 0;

	// NOTE: Screen size is normalized to 2x2 but (0,0) is at the center
	// of the screen (so screen is from -1 to 1 in both directions).

	// Update the vert array
	for ( i=1 ; i<=N ; i++ ) {
		x = (i-0.5f)*h-1; //x location of pixel (normalized)
		for ( j=1 ; j<=N ; j++ ) {
			y = (j-0.5f)*h-1; //y location of pixel (normalized)
			line_vertices[k++] = x;
			line_vertices[k++] = y;
			line_vertices[k++] = (x+u[IX(i,j)]);  
			line_vertices[k++] = (y+v[IX(i,j)]);
		}
	}

	// Update hardware buffers
	if( buffers_allocated )
	{
		// Update vertex buffer only
		glBindBuffer( GL_ARRAY_BUFFER, velocity_vbo[POSITION_OBJECT] );
		glBufferData( GL_ARRAY_BUFFER, size*4*sizeof(float), line_vertices, GL_DYNAMIC_DRAW ); // Discards
	}
}

static void update_density ( void )
{
	density_alpha = 0.5f;
	int    i, j, m;
	float  d00, d01, d10, d11;
	float* buffer = NULL;
	int    size = (N+2)*(N+2);


	m = 0;
	for ( i=0 ; i<=N ; i++ ) {
		for ( j=0 ; j<=N ; j++ ) {

			d00 = dens[IX(i,j)];
			d01 = dens[IX(i,j+1)];
			d10 = dens[IX(i+1,j)];
			d11 = dens[IX(i+1,j+1)];

			density_colors[m++] = d00;
			density_colors[m++] = d00;
			density_colors[m++] = d00;
			density_colors[m++] = density_alpha;

			density_colors[m++] = d10;
			density_colors[m++] = d10;
			density_colors[m++] = d10;
			density_colors[m++] = density_alpha;

			density_colors[m++] = d01;
			density_colors[m++] = d01;
			density_colors[m++] = d01;
			density_colors[m++] = density_alpha;

			density_colors[m++] = d01;
			density_colors[m++] = d01;
			density_colors[m++] = d01;
			density_colors[m++] = density_alpha;
			
			density_colors[m++] = d10;
			density_colors[m++] = d10;
			density_colors[m++] = d10;
			density_colors[m++] = density_alpha;
			
			density_colors[m++] = d11;
			density_colors[m++] = d11;
			density_colors[m++] = d11;
			density_colors[m++] = density_alpha;
		}
	}

	// Update hardware buffers
	if( buffers_allocated )
	{
		// Update color buffer only
		glBindBuffer( GL_ARRAY_BUFFER, density_vbo[COLOR_OBJECT] );
		glBufferData( GL_ARRAY_BUFFER, size*24*sizeof(float), density_colors, GL_DYNAMIC_DRAW ); // Discards
	}
}

// GL Render Calls -------------------------------------------------------------------
static void draw_velocity ( void )
{
	// Bind the index buffer ( Enables VBO Operation! )
	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, velocity_vbo[INDEX_OBJECT] );
	// Bind our vertex buffer ( Enables VBO Operation! )
	glBindBuffer( GL_ARRAY_BUFFER, velocity_vbo[POSITION_OBJECT] );
	// Declare the bound GL_ARRAY_BUFFER as our vertex pointer with an offset of 0
	glVertexAttribPointer( gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, 0 );
	// Enable Vertex Arrays
	glEnableVertexAttribArray( gvPositionHandle );
	checkGlError( "glEnableVertexAttribArray" );
	// Set the color of the lines
	glUniform1i( gVelocityHandle, (GLint)1 );
	// Perform the draw
	glDrawElements( GL_LINES, ( (N)*(N)*2 ), GL_UNSIGNED_SHORT, 0 );
	// Disable Vertex Arrays
	glDisableVertexAttribArray( gvPositionHandle );
	// Bind Buffers to 0 to re-enable normal pointer operation
	glBindBuffer( GL_ARRAY_BUFFER, 0 );
	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
}

static void draw_density ( void )
{
	// Bind the index buffer ( Enables VBO Operation! )
	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, density_vbo[INDEX_OBJECT] );
	// Bind our vertex buffer ( Enables VBO Operation! )
	glBindBuffer( GL_ARRAY_BUFFER, density_vbo[POSITION_OBJECT] );
	// Declare the bound GL_ARRAY_BUFFER as our vertex pointer with an offset of 0
	glVertexAttribPointer( gvPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, 0 );
	// Enable The Position Array
	glEnableVertexAttribArray( gvPositionHandle );
	// Bind the color buffer ( Enables VBO Operation! )
	glBindBuffer( GL_ARRAY_BUFFER, density_vbo[COLOR_OBJECT] );
	// Declare the bound GL_ARRAY_BUFFER as our color pointer with an offset of 0
	glVertexAttribPointer( gvColorHandle, 4, GL_FLOAT, GL_FALSE, 0, 0 );
	// Enable The Color Array
	glEnableVertexAttribArray( gvColorHandle );
	// Set velocity mode off
	glUniform1i( gVelocityHandle, 0 );
	// Perform the draw
	glDrawElements( GL_TRIANGLES, ( (N+1)*(N+1)*6 ), GL_UNSIGNED_SHORT, 0 );
	// Disable Color Arrays
	glDisableVertexAttribArray( gvColorHandle );
	// Disable Vertex Arrays
	glDisableVertexAttribArray( gvPositionHandle );
	// Bind Buffers to 0 to re-enable normal pointer operation
	glBindBuffer( GL_ARRAY_BUFFER, 0 );
	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
}

static void draw_particle( void )
{
	int i,j;
	float h;
	float part_points[6];
	float part_colors[3];
	h = (1.0f/N)*2;
	i = (p_x+1)/h+0.5f;
	j = (p_y+1)/h+0.5f;

	p_x = p_x + u[IX(i,j)];
	p_y = p_y + v[IX(i,j)];

	part_colors[0] = 1.0;
	part_colors[1] = 0.0;
	part_colors[2] = 0.0;
	part_points[0] = p_x;
	part_points[1] = p_y;
	part_points[2] = p_x + .05;
	part_points[3] = p_y;

	//glEnableClientState( GL_VERTEX_ARRAY );
	//glEnableClientState( GL_COLOR_ARRAY );
	glLineWidth( 10.0 );

	glColorPointer( 3, GL_FLOAT, 0, part_colors );
	glVertexPointer( 2, GL_FLOAT, 0, part_points );
	glDrawArrays( GL_LINES, 0, 2 );

	//glDisableClientState( GL_COLOR_ARRAY );
	//glDisableClientState( GL_VERTEX_ARRAY );

}


// Input -----------------------------------------------------------------------------
// This function is on its way out
static void get_from_UI ( float * d, float * u, float * v )
{
	int i, j, size = (N+2)*(N+2);

	for ( i=0 ; i<size ; i++ ) {
		u[i] = v[i] = d[i] = 0.0f;
	}

	if ( !mouse_down[0] && !mouse_down[2] ) return;

	i = (int)((       mx /(float)win_x)*N+1);
	j = (int)(((win_y-my)/(float)win_y)*N+1);

	if ( i<1 || i>N || j<1 || j>N ) return;

	if ( mouse_down[0] ) {
		u[IX(i,j)] = force * (mx-omx);
		v[IX(i,j)] = force * (omy-my);
	}

	if ( mouse_down[2] ) {
		d[IX(i,j)] = source;
	}
	// DEBUG HACK
	mouse_down[2] = 0;
	// DEBUG HACK

	omx = mx;
	omy = my;

	return;
}

// App Framework Entry Points --------------------------------------------------------
// Called from the app framework.
int appInit()
{
	// Set Defaults for Fluid Simulation
	N = 32;
	dt = 0.1f;
	diff = 0.002f; // 0.018 - 0.001
	visc = 0.00f; // 0.02 - 0.01
	force = 0.2f; // - 0.2
	source = 20.0f; // - 50
	p_x = p_y = 0.0;

	// Attempt to allocate memory for the fluid simulation
	if( !allocate_data() ) return 0;
	clear_data();

	// Setup GL for Rendering
	printGLString("Version", GL_VERSION);
    printGLString("Vendor", GL_VENDOR);
    printGLString("Renderer", GL_RENDERER);
    printGLString("Extensions", GL_EXTENSIONS);

	//glEnable(GL_NORMALIZE);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_CULL_FACE);

	// Set hardware support level
	buffers_supported = 1; // Need to do checking here
	buffers_allocated = 0;

	// Ready and allocate buffers
	if( buffers_supported )
		setup_hardware_buffers();  // OPTIMIZATION:  At this point, it is safe to free index buffers

	// Create shader program and ready handle
	gProgram = CreateProgram( vPassthroughShader, fPassthroughShader );
	if( !gProgram )
	{
		errprintf( "Could not create shader program.\n" );
		return 0;
	}

	// Grab the shader attributes and uniforms
	gvPositionHandle = glGetAttribLocation( gProgram, "vPosition" );
	checkGlError( "glGetAttribLocation" );
	gvColorHandle = glGetAttribLocation( gProgram, "vColor" );
	checkGlError( "glGetAttribLocation" );
	gVelocityHandle = glGetUniformLocation( gProgram, "Velocity" );
	checkGlError( "glGetUniformLocation" );

	// Initialize input stuff
	// THIS IS A TEST OF THE VECTORS: DEBUG
	//omx = mx = 100; // X Coordinate
	//omx = my = 100; // Y Coordinate
	
	mouse_down[0] = 1;
	//mouse_down[2] = 1; //  Removed for droidVizSourceLocation
	// THIS IS A TEST OF THE VECTORS: END DEBUG

	return 1;
}

// Called from the app framework.
void appDeinit()
{
	// Deinitialize the application, free memory
	free_data();
}

static void prepareFrame( int width, int height )
{
	glViewport( 0, 0, width, height );
	checkGlError( "glViewport" );

	// Let the simulation know the width and height of the window (OPTIMIZATION)
	win_x = width;
	win_y = height;

	// Set the clear color/flags
	glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
	glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT );

	// Set up the shader
	glUseProgram( gProgram );
	checkGlError( "glUseProgram" );
}

// Render loop -----------------------------------------------------------------------
void appRender(long tick, int width, int height)
{
	if (!gAppAlive)
		return;

	// Prepare OpenGL ES for rendering of the frame.
	prepareFrame(width, height);

	// Grab Input
	get_from_UI ( dens_prev, u_prev, v_prev );

	// Perform Fluid Calculations
	vel_step( N, u, v, u_prev, v_prev, visc, dt );
	dens_step( N, dens, dens_prev, u, v, diff, dt );

	// Update Rendering Structures
	//update_velocity();
	update_density();

	// Perform Rendering
	// Density Function draws smoke from source points
	//SetupCircle();
	draw_density();
	// Velocity Function Draws Vector Field Lines
	//draw_velocity();
	// Particle Generator not yet working
	//draw_particle();
}

void AddDensity(int mouse_xloc, int mouse_yloc)
{
	mouse_down[2] = 1;
	omx = mx = mouse_xloc; // X Coordinate
	omx = my = mouse_yloc; // Y Coordinate
}

void SetupCircle()
{
	glGenBuffers(1, circle_vbo);
	// Velocity:
	// Allocate and fill Index buffer
	glBindBuffer( GL_ARRAY_BUFFER, circle_vbo );
	glBufferData( GL_ARRAY_BUFFER, 720*4*sizeof(float), circle_vertices, GL_STATIC_DRAW );

	int i;
	for (i = 0; i < 720; i += 2) {
    // x value
    circle_vertices[i]   = (cos(DEGREES_TO_RADIANS(i)) * 1);
    // y value
    circle_vertices[i+1] = (sin(DEGREES_TO_RADIANS(i)) * 1);
	}

	//glBindBuffer( GL_ARRAY_BUFFER, density_vbo[POSITION_OBJECT] );
	// Perform the draw
	//glDrawElements( GL_TRIANGLES_FAN, ( (N+1)*(N+1)*6 ), GL_UNSIGNED_SHORT, 0 );
	glDrawArrays(GL_TRIANGLE_FAN, 0, 360);
    //glEnableClientState(GL_VERTEX_ARRAY);
}

void SetFragmentShader(char *fragment_shader)
{
	fPassthroughShader = fragment_shader;
}
void SetVertexShader(char *vertex_shader)
{
	vPassthroughShader = vertex_shader;
}
void SetFragmentShaderPreset()
{
	fPassthroughShader = fPassthroughShaderPreset;
}
void SetVertexShaderPreset()
{
	vPassthroughShader = vPassthroughShaderPreset;
}

// Allow Dynamic Changing of Shader
void changeShader_helper(int shader_code)
{
	ChangeShader(shader_code);
}
